import json
import re
import threading
from typing import Dict, Optional, Union

import Global
from HTTP.HttpUtil import HttpUtil



class ProxyUtil:
    _instance = None
    _lock = threading.Lock()
    
    def __new__(cls):
        if cls._instance is None:
            with cls._lock:
                if cls._instance is None:
                    cls._instance = super().__new__(cls)
                    cls._instance._ip_list = []
                    cls._instance._ip_lock = threading.Lock()
                    cls._instance._blacklist = set()  # 初始化黑名单集合
        return cls._instance
    
    @staticmethod
    def Parse(proxy_url: str) -> Dict[str, Optional[Union[str, int]]]:
        """
        解析 SOCKS5/HTTP/HTTPS 代理字符串，提取协议类型、IP、端口、账号和密码
        支持的格式：
            - socket://user:pass@host:port
            - socket://host:port
            - http://user:pass@host:port
            - http://host:port
            - https://user:pass@host:port
            - https://host:port

        返回字典格式：
            {
                "type": "socks5"|"http"|"https",
                "host": str,
                "port": int,
                "username": Optional[str],
                "password": Optional[str]
            }

        如果解析失败，抛出 ValueError。
        """
        # 统一转换为小写并去除两端空格
        proxy_url = proxy_url.strip()

        # 正则表达式匹配所有代理类型
        # pattern = re.compile(
        #     r'^(?P<type>socks5|http|https)://'  # 协议类型
        #     r'(?:(?P<user>[^:@]+)(?::(?P<pass>[^@]+))?@)?'  # 用户名和密码（可选）
        #     r'(?P<ip>[^:/]+)'  # 主机名/IP
        #     r':(?P<port>\d+)$'  # 端口
        # )
        pattern = re.compile(
            r'^(?P<type>socket5|http|https)://'  # 协议类型
            r'(?:(?P<user>[^:@]+?)(?::(?P<pass>[^@]+?))?@)?'  # 用户名和密码（可选）
            r'(?P<ip>[^:@]+?)'  # 主机名/IP
            r':(?P<port>\d+)'  # 端口
            r'/?$'  # 可选的结尾斜杠
        )
        match = pattern.match(proxy_url)
        if not match:
            raise ValueError(f"不正确的代理格式: {proxy_url}")

        proxy_type = match.group('type')
        username = match.group('user')
        password = match.group('pass')
        host = match.group('ip')
        port = int(match.group('port'))

        return {
            "type": proxy_type,
            "ip": host,
            "port": port,
            "user": username,
            "pass": password
        }
        
    @staticmethod
    def fetch_ips_from_api(api_url):
        """
        从API接口获取IP地址并存入全局数组
        :param api_url: API接口地址(可选，优先使用配置中的地址)
        :return: 是否成功获取IP
        """
        http = HttpUtil()
        response,http_code,erro = http.get(api_url)
        try:
            rsp = json.loads(response)
            status = rsp.get("status", 0)
            info = rsp.get("info", "")
            return False, f"{rsp}"
        except json.JSONDecodeError as e:
            pass
        ips = [ip.strip() for ip in response.split('\r\n') if ip.strip()]
        if ips:
            filtered_ips = []
            api_repeat = Global.G_GlobalConfig.get('api_repeat', False)
            for ip in ips:
                if api_repeat:
                    if ip not in ProxyUtil()._blacklist:
                        filtered_ips.append(ip)
                else:
                    filtered_ips.append(ip)
            if len(filtered_ips)==0:
                return False,'无可用IP'
            else:
                ProxyUtil()._ip_list.extend(filtered_ips)
            return True, ''
        return False, '获取IP失败'
    
    @staticmethod
    def remove_ip_from_blacklist(ip_info):
        """
        从黑名单中移除指定的IP地址
        用于外部调用，当切换IP时清除黑名单中的IP，避免黑名单过大
        :param ip_address: 要移除的IP地址，支持完整代理字符串或ip:port格式
        :return: 是否成功移除
        """
        if not ip_info:
            ProxyUtil()._blacklist.clear()
            return True

        ip_address =f"{ip_info['ip']}:{ip_info['port']}"
        with ProxyUtil()._ip_lock:
            try:
                if ip_address in ProxyUtil()._blacklist:
                    ProxyUtil()._blacklist.remove(ip_address)
                    return True
                return False
            except Exception as e:
                # 如果解析失败，尝试直接移除原始字符串
                if ip_address in ProxyUtil()._blacklist:
                    ProxyUtil()._blacklist.remove(ip_address)
                    return True
                return False

    @staticmethod
    def load_ips_from_file(file_path):
        """
        从文件导入IP地址并存入全局数组
        :param file_path: 代理文件路径
        :return: 是否成功导入IP
        """
        try:
            with open(file_path, 'r', encoding='utf-8') as f:
                lines = f.readlines()

            ips = []
            for line in lines:
                line = line.strip()
                if not line:
                    continue

                # 按:或/分割获取ip、端口、账号、密码
                parts = re.split(r'[:/|]', line)
                if len(parts) >= 2:
                    ip = parts[0]
                    port = parts[1]
                    username = parts[2] if len(parts) > 2 else None
                    password = parts[3] if len(parts) > 3 else None
                    protocol = "socket5"
                    if username and password:
                        ips.append(f"{protocol}://{username}:{password}@{ip}:{port}")
                    else:
                        ips.append(f"{protocol}://{ip}:{port}")

            if ips:
                ProxyUtil()._ip_list = ips
                return True, ''
            return False, '文件内容格式不正确'

        except Exception as e:
            return False, f"读取代理文件失败: {str(e)}"
    @staticmethod 
    def get_ip():
        """
        从全局数组中获取一个IP地址
        根据配置决定使用API还是文件导入方式
        :return: IP地址或None
        """
        if not Global.G_GlobalConfig.get('enable_proxy', False):
            return None
            
        proxy_method = Global.G_GlobalConfig.get('proxy_method')
        protocol = Global.G_GlobalConfig.get('ip_protocol', 'http')
        if proxy_method == 'api':
            api_url = Global.G_GlobalConfig.get('api_url', "")
            if not api_url:
                raise Exception("API提取网址未设置")
            
            api_repeat = Global.G_GlobalConfig.get('api_repeat', False)

            # 如果api_repeat为假，清空黑名单
            if not api_repeat:
                with ProxyUtil()._ip_lock:
                    ProxyUtil()._blacklist.clear()
            
            # 预检查减少锁竞争
            if ProxyUtil()._ip_list:
                with ProxyUtil()._ip_lock:
                    if ProxyUtil()._ip_list:
                        ip = ProxyUtil()._ip_list.pop(0)
                        # 如果启用api_repeat，将使用的IP加入黑名单
                        if api_repeat:
                            ProxyUtil()._blacklist.add(ip)
                        return ProxyUtil.Parse(f"{protocol}://{ip}")
            
            # 双重检查获取新IP
            with ProxyUtil()._ip_lock:
                if not ProxyUtil()._ip_list:
                    isSuccess, erro = ProxyUtil.fetch_ips_from_api(api_url)
                    if not isSuccess:
                        raise Exception(erro)
                    
                if ProxyUtil()._ip_list:
                    ip = ProxyUtil()._ip_list.pop(0)
                    # 如果启用api_repeat，将使用的IP加入黑名单
                    if api_repeat:
                        ProxyUtil()._blacklist.add(ip)
                    return ProxyUtil.Parse(f"{protocol}://{ip}")
        
        elif proxy_method == 'file':
            file_path = Global.G_GlobalConfig.get('proxy_file_path', "")
            if not file_path:
                raise Exception("代理文件路径未设置")
                
            with ProxyUtil()._ip_lock:
                # 如果ip_list为空，尝试从黑名单恢复或重新加载文件
                if not ProxyUtil()._ip_list:
                    # 如果黑名单不为空，将黑名单中的IP恢复到ip_list
                    if ProxyUtil()._blacklist:
                        #ProxyUtil()._ip_list = list(ProxyUtil()._blacklist)
                        ProxyUtil()._blacklist.clear()
                    else:
                        # 如果黑名单也为空，重新从文件加载
                        isSuccess, erro = ProxyUtil.load_ips_from_file(file_path)
                        if not isSuccess:
                            raise Exception(erro)

                if ProxyUtil()._ip_list:
                    import random
                    ip = random.choice(ProxyUtil()._ip_list)
                    parse_ip = ProxyUtil.Parse(f"{ip}")
                    # 从ip_list中移除选中的IP
                    ProxyUtil()._ip_list.remove(ip)
                    ProxyUtil()._blacklist.add(f"{parse_ip['ip']}:{parse_ip['port']}")
                    return parse_ip
                    
        return None
        
    @staticmethod
    def GetInternetIP(proxy):
        #获取公网IP
        def is_valid_ipv4(ip):
            pattern = r'^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$'
            return bool(re.match(pattern, ip))
        #ip = HttpUtil.get("https://icanhazip.com/",proxy=proxy)
        http = HttpUtil()
        resp,status_code,erro =http.get("https://httpbin.org/ip", proxy=proxy)
        ip="IP异常"
        try:
            ip_dict= json.loads(resp)
            ip = ip_dict.get('origin','')
        except Exception as e:
            print(f"获取公网IP异常:{e}")
        return ip